/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.geektimes.rest.client.invocation;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;
import org.geektimes.rest.core.DefaultResponse;

import javax.ws.rs.HttpMethod;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;

import static org.geektimes.rest.util.URLUtils.DEFAULT_ENCODING;

/**
 * HTTP GET Method {@link Invocation}
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
public abstract class HttpInvocation implements Invocation {

    private final URL url;

    private final MultivaluedMap<String, Object> headers;

    protected Map<String, Object> properties = new HashMap<>();

    protected Entity<?> entity;

    HttpInvocation(URI uri, MultivaluedMap<String, Object> headers, Map<String, Object> properties) {
        this.headers = headers;
        this.properties = properties;
        try {
            this.url = uri.toURL();
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException();
        }
    }

    HttpInvocation(URI uri, MultivaluedMap<String, Object> headers, Map<String, Object> properties, Entity<?> entity) {
        this.headers = headers;
        this.properties = properties;
        this.entity = entity;
        try {
            this.url = uri.toURL();
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException();
        }
    }

    HttpInvocation(URI uri, MultivaluedMap<String, Object> headers) {
        this.headers = headers;
        try {
            this.url = uri.toURL();
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException();
        }
    }

    @Override
    public Invocation property(String name, Object value) {
        return this;
    }

    @Override
    public Response invoke() {
        HttpURLConnection connection;
        try {
            connection = (HttpURLConnection) url.openConnection();
            buildRequest(connection);
            setRequestHeaders(connection);
            return buildResponse(connection);
        } catch (IOException e) {
            DefaultResponse response = new DefaultResponse();
            response.setStatus(Response.Status.BAD_REQUEST.getStatusCode());
            response.setEntity(Response.Status.BAD_REQUEST.getReasonPhrase());
            return  response;
        }
    }

    protected abstract void buildRequest(HttpURLConnection connection) throws IOException;

    private void setRequestHeaders(HttpURLConnection connection) {
        for (Map.Entry<String, List<Object>> entry : headers.entrySet()) {
            String headerName = entry.getKey();
            for (Object headerValue : entry.getValue()) {
                connection.setRequestProperty(headerName, headerValue.toString());
            }
        }
    }

    private DefaultResponse buildResponse(HttpURLConnection connection){
        try {
            DefaultResponse response = new DefaultResponse();
            int statusCode = connection.getResponseCode();
            response.setStatus(statusCode);
            Response.Status status = Response.Status.fromStatusCode(statusCode);
            switch (status){
                case OK: {
                    InputStream inputStream = connection.getInputStream();
                    Object value = IOUtils.toString(inputStream, DEFAULT_ENCODING);
                    response.setEntity(value);
                    break;
                }
                default:{
                    response.setEntity(status.getReasonPhrase());
                    break;
                }
            }
            return response;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            connection.disconnect();
        }
    }

    @Override
    public <T> T invoke(Class<T> responseType) {
        Response response = invoke();
        return response.readEntity(responseType);
    }

    @Override
    public <T> T invoke(GenericType<T> responseType) {
        Response response = invoke();
        return response.readEntity(responseType);
    }

    @Override
    public Future<Response> submit() {
        return null;
    }

    @Override
    public <T> Future<T> submit(Class<T> responseType) {
        return null;
    }

    @Override
    public <T> Future<T> submit(GenericType<T> responseType) {
        return null;
    }

    @Override
    public <T> Future<T> submit(InvocationCallback<T> callback) {
        return null;
    }
}
